package mspbots.cw.client.core.data.boards;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mspbots.cw.client.core.AbstractToolsUtil;
import mspbots.cw.client.core.data.BaseApiService;
import mspbots.cw.client.core.app.SyncRequest;
import mspbots.cw.client.core.client.ClientProperties;
import mspbots.cw.common.QueryParams;
import mspbots.cw.common.RabbitKeys;
import mspbots.cw.common.ResultMsg;
import mspbots.cw.common.ResultSync;
import mspbots.cw.common.callback.CallbackAction;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.util.UUID;


/**
 * com.mspbots.sync.wise.service.BoardService
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/11/13
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class BoardsServiceImpl extends AbstractToolsUtil implements BaseApiService {

    @Override
    public Mono<JsonNode> count(Long tenantId, QueryParams queryParams) {
        ResultSync resultSync = ResultSync.withDefault("BOARDS-COUNT");
        return super.loadProperties(tenantId)
                .flatMap(clientProperties -> this.count(clientProperties, queryParams))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync));
    }

    @Override
    public Flux<ObjectNode> find(Long tenantId, QueryParams queryBody) {
        ResultSync resultSync = ResultSync.withDefault("BOARDS-SEARCH");
        return super.loadProperties(tenantId)
                .flatMapMany(clientProperties -> this.get(clientProperties, queryBody))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync));
    }

    private Mono<ObjectNode> setStatuses(ObjectNode boardNode) {
        ResultSync resultSync = ResultSync.withDefault("BOARD-STATUSES-SYNC");
        return super.cwWebClient.fetch("/service/boards/" + boardNode.get("cwId").longValue() + "/statuses",
                QueryParams.withDefault().setConditions("inactive=false").setOrderBy("id desc"))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .collectList()
                .map(statusesNodeList -> {
                    if (statusesNodeList.size() > 0) {
                        boardNode.putPOJO("status",
                                this.objectMapper.convertValue(statusesNodeList, ArrayNode.class));
                    }
                    return boardNode;
                }).onErrorReturn(boardNode).defaultIfEmpty(boardNode);

    }

    private Mono<ObjectNode> setSubtypes(ObjectNode boardNode) {
        ResultSync resultSync = ResultSync.withDefault("BOARD-SUBTYPES-SYNC");
        return super.cwWebClient.fetch("/service/boards/" + boardNode.get("cwId").longValue() + "/subtypes",
                QueryParams.withDefault().setConditions("inactiveFlag=false"))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .collectList()
                .map(statusesNodeList -> {
                    if (statusesNodeList.size() > 0) {
                        boardNode.putPOJO("subtypes",
                                this.objectMapper.convertValue(statusesNodeList, ArrayNode.class));
                    }
                    return boardNode;
                }).onErrorReturn(boardNode).defaultIfEmpty(boardNode);

    }

    private Mono<ObjectNode> setTypes(ObjectNode boardNode) {
        ResultSync resultSync = ResultSync.withDefault("BOARD-TYPES-SYNC");
        return super.cwWebClient.fetch("/service/boards/" + boardNode.get("cwId").longValue() + "/types",
                QueryParams.withDefault().setConditions("inactiveFlag=false"))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .collectList()
                .map(statusesNodeList -> {
                    if (statusesNodeList.size() > 0) {
                        boardNode.putPOJO("types",
                                this.objectMapper.convertValue(statusesNodeList, ArrayNode.class));
                    }
                    return boardNode;
                }).onErrorReturn(boardNode).defaultIfEmpty(boardNode);

    }

    private Mono<ObjectNode> setItems(ObjectNode boardNode) {
        ResultSync resultSync = ResultSync.withDefault("BOARD-ITEMS-SYNC");
        return cwWebClient.fetch("/service/boards/" + boardNode.get("cwId").longValue() + "/items",
                QueryParams.withDefault().setConditions("inactiveFlag=false"))
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .collectList()
                .map(statusesNodeList -> {
                    if (statusesNodeList.size() > 0) {
                        boardNode.putPOJO("items",
                                this.objectMapper.convertValue(statusesNodeList, ArrayNode.class));
                    }
                    return boardNode;
                }).onErrorReturn(boardNode).defaultIfEmpty(boardNode);
    }

    private Mono<ObjectNode> setExcludedMembers(ObjectNode boardNode) {
        ResultSync resultSync = ResultSync.withDefault("BOARD-EXCLUDED-MEMBERS-SYNC");
        return cwWebClient.fetch("/service/boards/" + boardNode.get("cwId").longValue() + "/excludedMembers",
                QueryParams.withDefault())
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .collectList()
                .map(statusesNodeList -> {
                    if (statusesNodeList.size() > 0) {
                        boardNode.putPOJO("excludedMembers",
                                this.objectMapper.convertValue(statusesNodeList, ArrayNode.class));
                    }
                    return boardNode;
                }).onErrorReturn(boardNode).defaultIfEmpty(boardNode);
    }


    private Flux<ObjectNode> get(ClientProperties clientProperties, QueryParams queryParams) {
        return this.cwWebClient.fetch("/service/boards", queryParams)
                .flatMap(this::buildBoard)
                .contextWrite(ctx -> ctx.put("properties", clientProperties));
    }

    private Mono<ObjectNode> buildBoard(JsonNode resultNode) {
        ObjectNode ticketNode = resultNode.deepCopy();
        ticketNode.put("cwId", resultNode.get("id").longValue());
        return Mono.just(ticketNode)
                .flatMap(this::setStatuses)
                .flatMap(this::setTypes);
//                .flatMap(this::setExcludedMembers)
//                .flatMap(this::setItems)
//                .flatMap(this::setSubtypes);
    }

    private Mono<JsonNode> count(ClientProperties clientProperties, QueryParams queryParams) {
        return this.cwWebClient.get("/service/boards/count", queryParams)
                .contextWrite(ctx -> ctx.put("properties", clientProperties));
    }

    @Override
    public Mono<Void> syncSlow(ClientProperties clientProperties) {
        ResultSync resultSync = ResultSync.withDefault("BOARDS-SYNC");
        return this.get(clientProperties, QueryParams.withDefault())
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .doOnNext(result -> this.syncResult(clientProperties.getTenantId(), resultSync.getRequestId(), result))
                .doOnNext(result -> this.syncAssociations(result.get("id").intValue(), clientProperties))
//                .doOnNext(result -> this.syncAutoAssignResource(result.get("id").intValue(), clientProperties))
                .then();
    }

    @Override
    public Mono<Void> manualSync(ClientProperties properties, SyncRequest syncRequest) {
        ResultSync resultSync = ResultSync.withDefault("MANUAL-BOARDS-SYNC");
        return this.get(properties, QueryParams.withDefault())
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .doOnNext(node -> this.syncResult(properties.getTenantId(),
                        resultSync.getRequestId(), node))
                .doOnNext(result -> this.syncAssociations(result.get("id").intValue(), properties))
                .then();
    }

    private void syncResult(Long tenantId, UUID id, JsonNode resultNode) {
        this.sendEntity(RabbitKeys.BOARDS_SYNC_QUEUE_KEY, ResultMsg.builder()
                .requestId(id).id(resultNode.get("id").longValue())
                .action(CallbackAction.none.name()).entity(resultNode).tenantId(tenantId)
                .syncDate(LocalDateTime.now()).build());
    }


    private void syncAssociations(Integer boardId, ClientProperties clientProperties) {
        ResultSync resultSync = ResultSync.withDefault("BOARDS-ASSOCIATIONS-SYNC");
        this.getAssociations(clientProperties, boardId)
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .subscribe(resultNode -> {
                    ObjectNode entryNode = resultNode.deepCopy();
                    entryNode.put("tenantId", clientProperties.getTenantId());
                    entryNode.put("boardId", boardId);
                    this.sendEntity(RabbitKeys.ASSOCIATIONS_BOARDS_SYNC_QUEUE_KEY, ResultMsg.builder()
                            .requestId(resultSync.getRequestId()).id(resultNode.get("id").longValue())
                            .action(CallbackAction.none.name()).entity(resultNode).tenantId(clientProperties.getTenantId())
                            .syncDate(LocalDateTime.now()).build());
                }, error -> log.error("Sync BOARDS-ASSOCIATIONS-SYNC error, msg: {} ", error.getMessage()));
    }


    private void syncAutoAssignResource(Integer boardId, ClientProperties clientProperties) {
        ResultSync resultSync = ResultSync.withDefault("BOARDS-AUTO-ASSIGN-RESOURCE-SYNC");
        this.getAutoAssignResource(clientProperties, boardId)
                .contextWrite(ctx -> ctx.put("resultSync", resultSync))
                .subscribe(resultNode -> {
                    ObjectNode entryNode = resultNode.deepCopy();
                    entryNode.put("boardId", boardId);
                    this.sendEntity(RabbitKeys.AUTO_ASSIGN_RESOURCE_BOARDS_SYNC_QUEUE_KEY, ResultMsg.builder()
                            .requestId(resultSync.getRequestId()).id(resultNode.get("id").longValue())
                            .action(CallbackAction.none.name()).entity(resultNode).tenantId(clientProperties.getTenantId())
                            .syncDate(LocalDateTime.now()).build());
                }, error -> log.error("Sync BOARDS-AUTO-ASSIGN-RESOURCE-SYNC error, msg: {} ", error.getMessage()));
    }


    private Flux<JsonNode> getAssociations(ClientProperties clientProperties, Integer boardId) {
        return this.cwWebClient.fetch("/service/boards/" + boardId + "/typeSubTypeItemAssociations",
                QueryParams.withDefault())
                .contextWrite(ctx -> ctx.put("properties", clientProperties));
    }

    private Flux<JsonNode> getAutoAssignResource(ClientProperties clientProperties, Integer boardId) {
        return this.cwWebClient.fetch("/service/boards/" + boardId + "/autoAssignResources",
                QueryParams.withDefault())
                .contextWrite(ctx -> ctx.put("properties", clientProperties));
    }

}
